/* Copyright (C) 2010 Fredrique Samuels, fredriquesamuels@gmail.com */

/*
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "display_engine.h"

#include <stdio.h>
#include <iostream>
#include <map>

namespace tri {

TSelectBuffer::TSelectBuffer(unsigned int* b, int s) {
	_size = s;

	if (_size>MAX_SELECT_BUFFER_SIZE) {
		_size = MAX_SELECT_BUFFER_SIZE;
	}

	for (unsigned int i=0; i<_size; i++) {
		buf[i] = b[i];
	}
}

TSelectBuffer::~TSelectBuffer() {
}

unsigned int TSelectBuffer::size() {
	return _size;
}

unsigned int TSelectBuffer::get(unsigned int i) {
	if (i<0|| i>=_size) {
		return 0;
	}
	return buf[i];
}


bool TDisplayEngine::s_IsPicking = false;

bool TDisplayEngine::isPicking(){
	return s_IsPicking;
}

TDisplayEngine::TDisplayEngine() :
	TContext(), m_isInit(false){
	m_MouseEvent.set(-1,-1,0,0,tri::NONE, tri::NONE);
}

TDisplayEngine::~TDisplayEngine() {

}

void TDisplayEngine::display() {
	if (!m_isInit) {
		graphics::initGraphics();
		m_isInit=true;
	}
	
	
	
	
	glViewport(0, 0, (int)m_ViewPort.getRight(), (int)m_ViewPort.getBottom());
	GLCLEAR(GLCLEARALL);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(90, 1, 0.1, 100000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	m_MainSceneChache.setCamera();
	m_MainSceneChache.update();
	m_MainSceneChache.render();
	
	glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(90, 1, 0.1, 100000);
	glMatrixMode(GL_MODELVIEW);
	
	glViewport(0, 0, (int)m_ViewPort.getRight(), (int)m_ViewPort.getBottom());
	m_OverlaySceneChache.setCamera();
	m_OverlaySceneChache.update();
	m_OverlaySceneChache.render();
	
	bool doMainScenePicking = false;
	TScene* scene = m_OverlaySceneChache.getActiveScene();
	if( scene != NULL ){
		//Get all entities under the mouse pointer
		pick(scene, &m_OverlaySelectBuffer);
		//copy list to mouse event object
		for (unsigned int i=0;i< m_OverlaySelectBuffer.size();i++){
			//std::cout<<m_MainSelectBuffer.get(i)<<" ";
			m_MouseEvent.IDList.push_back(m_OverlaySelectBuffer.get(i));
		}
		
		
		//get last object draw closest to the camera.
		if (m_OverlaySelectBuffer.size() > 0){
			TEntity* entity = scene->getEntity(m_OverlaySelectBuffer.get(0));
			if ( entity!=NULL ){
				TEventsProcess::process_mouse_event(m_MouseEvent, entity);
			}
		}else{
			doMainScenePicking = true;
		}
	}else{
		doMainScenePicking = true;
	}
	
	scene = m_MainSceneChache.getActiveScene();
	if( scene != NULL && doMainScenePicking){
		//Get all entities under the mouse pointer
		pick(scene, &m_MainSelectBuffer);
		//copy list to mouse event object
		for (unsigned int i=0;i< m_MainSelectBuffer.size();i++){
			//std::cout<<m_MainSelectBuffer.get(i)<<" ";
			m_MouseEvent.IDList.push_back(m_MainSelectBuffer.get(i));
		}
		
		
		//get last object draw closest to the camera.
		if (m_MainSelectBuffer.size() > 0){
			TEntity* entity = scene->getEntity(m_MainSelectBuffer.get(0));
			if ( entity!=NULL ){
				TEventsProcess::process_mouse_event(m_MouseEvent, entity);
			}
		}else{
			TEventsProcess::process_mouse_event(m_MouseEvent, NULL);
		}
	}

	m_MouseEvent.set(m_MouseEvent.getX(),m_MouseEvent.getY(),0,0,tri::NONE, tri::NONE);
	
//	gluLookAt(0,0,10,
//			0,0,0,
//			0,1,0);
//	
//	m_Glyph.setSize(2,2);
//	m_Glyph.render();
//	
//	m_Glyph2.setSize(2,2);
//	m_Glyph2.setPosition(3,0,0);
//	m_Glyph.addEntity(&m_Glyph2);
	 
//	
//	
//	
//	//std::cout<<"diaplayEngine::display()::Request Repaint? :"<<_world.repaintRequested()<<" "<<_mOverLayWorld.repaintRequested() << " "<<repaintScreen<<std::endl;
//	bool repaintOnRequest = _world.repaintRequested() or _mOverLayWorld.repaintRequested() or repaintScreen;
//	
//	if(repaintOnRequest){
//		coolDownCount = 5;
//	}
//	_world.update();
//	_mOverLayWorld.update();
//	
//	if (coolDownCount>0 || !optimizeCPU){
//		frames++;
//		//std::cout<<"PAINTIN::"<<frames<<std::endl;
//		
//		glViewport(0, 0, scr_size[0], scr_size[1]);
//		GLCLEAR(GLCLEARALL);
//
//		/**
//		 * LOAD BACKGROUND IMAGE
//		 */
//		std::string _image = getBackGround();
//		try {
//			if (_world.getBackGround() != "") {
//
//				texture* tex = tri::get_texture(_image);
//				tri::rectangle _tc;
//				if (tex!=NULL) {
//					if (!tex->created()) {
//						tex->load(_image);
//					}
//					_tc = tex->get_tc();
//					tex->bind();
//				} else if(sys::isfile(_image)) {
//					tri::create_texture(_image);
//					texture* tex = tri::get_texture(_image);
//					if(tex!=NULL) {
//						tex->load(_image);
//						tex->bind();
//					}
//				} else {
//					unbind_all_textures();
//				}
//
//				glMatrixMode(GL_PROJECTION);
//				glLoadIdentity();
//				gluPerspective(90, 1, 0.1, 100000);
//				glMatrixMode(GL_MODELVIEW);
//				glLoadIdentity();
//
//				gluLookAt(0,0,10,
//						0,0,0,
//						0,1,0);
//
//				tri::graphics::render_glyph(20,20, NULL, NULL, NULL, NULL, &_tc);
//				unbind_all_textures();
//			}
//		} catch( tri::TriException e) {
//			std::cerr<<"Unknown exception loading background image "<<_image<<"!!!!";
//		} catch(...) {
//			std::cerr<<"Unknown exception loading background image "<<_image<<"!!!!";
//		}
//
//		//std::cout<<"PAINTIN!!!!"<<std::endl;
//		
//
//		camera* cam = _world.getCamera();
//		if ( cam != NULL ){
//			abstract_entity::setActiveCamera(cam);
//			cam->action();
//			_world.render();
//		}
//		pick(&_world, &this->_select_buffer);
//		
//		glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//		glLoadIdentity();
//		
//		cam = _mOverLayWorld.getCamera();
//		if ( cam != NULL ){
//			abstract_entity::setActiveCamera(cam);
//			cam->action();
//			_mOverLayWorld.render();
//		}
//		
//		pick(&_mOverLayWorld, &this->_mOverLaySelectBuffer);
//
//		entity* e= NULL;
//		_mouse_event.idList.clear();
//		if(_mOverLaySelectBuffer.size()>0){
//			unsigned int _id = _mOverLaySelectBuffer.get(0);
//			for (int i=0;i< _mOverLaySelectBuffer.size();i++){
//	//			std::cout<<_select_buffer.get(i)<<" ";
//				_mouse_event.idList.push_back(_mOverLaySelectBuffer.get(i));
//			}
//	//		std::cout<<std::endl;
//			e = _mOverLayWorld.getByEntityID(_id);
//		}else if (_select_buffer.size()>0) {
//			unsigned int _id = _select_buffer.get(0);
//			for (int i=0;i< _select_buffer.size();i++){
//	//			std::cout<<_select_buffer.get(i)<<" ";
//				_mouse_event.idList.push_back(_select_buffer.get(i));
//			}
//	//		std::cout<<std::endl;
//			e = _world.getByEntityID(_id);
//		}
//		
//		_mOverLayWorld.postDisplayUpdate();
//		_world.postDisplayUpdate();
//
//		tri::events::_events_process::process_mouse_event(_mouse_event, e);
//		_mouse_event.event = tri::NONE;
//		_mouse_event.button = tri::NONE;
//		_mouse_event.y_rel = 0;
//		_mouse_event.x_rel = 0;
//		_mouse_event.idList.clear();
//		repaintScreen = false;
//		coolDownCount--;
//	}
//	
//	
//	
}

void TDisplayEngine::resize(int width, int height) {
	m_ViewPort.setValue(0,0,width,height);
	
	
	TScene* scene = m_MainSceneChache.getActiveScene();
	if (scene != NULL){
		scene->onResize(width,height);
	}
	
	scene = m_OverlaySceneChache.getActiveScene();
	if (scene != NULL){
		scene->onResize(width,height);
	}
		
	
}

void TDisplayEngine::init() {
	tri::graphics::initGraphics();
}

void TDisplayEngine::keyDown(int key) {
	TScene* scene = m_MainSceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(tri::KEY_PRESSED,key);
	}
	
	scene = m_OverlaySceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(tri::KEY_PRESSED,key);
	}
	
	tri::TEventsProcess::process_key_event(tri::TKeyEvent(key,
			tri::KEY_PRESSED));
}

void TDisplayEngine::keyUp(int key) {
	TScene* scene = m_MainSceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(tri::KEY_RELEASED,key);
	}
	
	scene = m_OverlaySceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(tri::KEY_RELEASED,key);
	}
	tri::TEventsProcess::process_key_event(tri::TKeyEvent(key,
				tri::KEY_RELEASED));
}

void TDisplayEngine::mouseDrag(int x, int y, int xrel, int yrel, int button) {
	if (m_MouseEvent.getEvent()!=tri::NONE) {
		return;
	}
	m_MouseEvent.set(x,y,xrel,yrel,button,tri::MOUSE_DRAG);
	TMouseEvent::copy(m_MouseEvent, TMouseEvent::s_CurrentEvent);
	
	TScene* scene = m_OverlaySceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(MOUSE_DRAG);
	}
	
	scene = m_MainSceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(MOUSE_DRAG);
	}
}

void TDisplayEngine::mouseMove(int x, int y, int xrel, int yrel) {
	if (m_MouseEvent.getEvent()!=tri::NONE) {
		return;
	}
	m_MouseEvent.set(x,y,xrel,yrel,tri::NONE,tri::MOUSE_MOVE);
	TMouseEvent::copy(m_MouseEvent, TMouseEvent::s_CurrentEvent);
	
	TScene* scene = m_OverlaySceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(MOUSE_MOVE);
	}
	
	
	scene = m_MainSceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(MOUSE_MOVE);
	}

}

void TDisplayEngine::mouseUp(int x, int y, int button) {
	if (m_MouseEvent.getEvent()!=tri::NONE) {
		return;
	}
	m_MouseEvent.set(x,y,0,0,button,tri::BUTTON_RELEASED);
	TMouseEvent::copy(m_MouseEvent, TMouseEvent::s_CurrentEvent);
	
	TScene* scene = m_OverlaySceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(BUTTON_RELEASED);
	}
	
	scene = m_MainSceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(BUTTON_RELEASED);;
	}


}

void TDisplayEngine::mouseDown(int x, int y, int button) {
	if (m_MouseEvent.getEvent()!=tri::NONE) {
		return;
	}
	m_MouseEvent.set(x,y,0,0,button,tri::BUTTON_PRESSED);
	TMouseEvent::copy(m_MouseEvent, TMouseEvent::s_CurrentEvent);
	
	TScene* scene = m_OverlaySceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(BUTTON_PRESSED);
	}
	
	scene = m_MainSceneChache.getActiveScene();
	if (scene!=NULL){
		scene->emit(BUTTON_PRESSED);
	}
}

void TDisplayEngine::addScene(std::string key, TScene* scene){
	m_MainSceneChache.addScene(key,scene);
}

void TDisplayEngine::removeScene(std::string key){
	m_MainSceneChache.removeScene(key);
}

void TDisplayEngine::setActiveScene(std::string key){
	m_MainSceneChache.setActiveScene(key);
}

TScene* TDisplayEngine::getScene(std::string key){
	return m_MainSceneChache.getScene(key);
}

TScene* TDisplayEngine::getOverLayScene(std::string key){
	return m_OverlaySceneChache.getScene(key);
}

void TDisplayEngine::addOverLayScene(std::string key, TScene* scene){
	m_OverlaySceneChache.addScene(key,scene);
}

void TDisplayEngine::removeOverLayScene(std::string key){
	m_OverlaySceneChache.removeScene(key);
}

void TDisplayEngine::setActiveOverLayScene(std::string key){
	m_OverlaySceneChache.setActiveScene(key);
}

int TDisplayEngine::pick(TScene* w, TSelectBuffer *_select_buffer ) {
	_select_buffer->_size = 0;
	
	TCamera* cam = w->getCamera();
	if (cam == NULL){
		return 0;
	}
	s_IsPicking = true;
	int hits = 0; // The Number Of Entitys That We Selected

	// int[] buffer = new int[BUFSIZE]; // Set Up A Selection Buffer
	GLuint buf[512];

	// The Size Of The Viewport. [0] Is <x>, [1] Is <y>, [2] Is <length>,
	// [3] Is <width>
	int viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);

	glSelectBuffer(512, buf); // Tell OpenGL To Use Our

	// Array For Selection
	// Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Entity ID's and
	// Extents Are Stored In The Buffer.
	glRenderMode(GL_SELECT);

	glInitNames(); // Initializes The Name Stack
	glPushName(0); // Push 0 (At Least One Entry) Onto The Stack
	glMatrixMode(GL_PROJECTION); // Selects The Projection Matrix
	glPushMatrix(); // Push The Projection Matrix
	glLoadIdentity(); // Resets The Matrix
	// This Creates A Matrix That Will Zoom Up To A Small Portion Of The
	// Screen, Where The Mouse Is.

	gluPickMatrix(TMouseEvent::s_CurrentEvent.getX(), (viewport[3] - TMouseEvent::s_CurrentEvent.getY()), 1.0, 1.0,
			viewport);
	
	// Apply The Perspective Matrix
	//__setProjection_(viewport[2],viewport[3])
	cam->applyPerspective();

	glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
	glLoadIdentity();
	// Render The Targets To The Selection Buffer
	cam->applyView();
	w->render();

	glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
	glPopMatrix(); // Pop The Projection Matrix
	glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
	hits = glRenderMode(GL_RENDER); // Switch To Render Mode, Find Out

	s_IsPicking = false;

	_select_buffer->_size = 0;
	if (hits>0) {

		tri::utils::bubbleSortList4Uint32Decend(buf, hits*4, 2);

		int count = hits*4-1;
		_select_buffer->_size = hits;
		if (hits>MAX_SELECT_BUFFER_SIZE) {
			count = MAX_SELECT_BUFFER_SIZE*4-1;
			_select_buffer->_size = MAX_SELECT_BUFFER_SIZE;
		}

		int j=0;
		for (int i=count; i>0; i-=4) {
			_select_buffer->buf[j] = buf[i];
			j++;
		}

	}
	return hits;
}

//
//void TDisplayEngine::addScene(std::string key, scene* s) {
//	_world.addScene(key, s);
//}
//
//void TDisplayEngine::gotoScene(std::string key) {
//	_world.gotoScene(key);
//	_world.resize(scr_size[0],scr_size[1]);
//}
//
//void TDisplayEngine::removeScene(std::string key){
//	std::vector<std::string> list;
//	if (_world.getScene(key)!=NULL){
//		_world.removeScene(key);
//		_world.getSceneList(list);
//		if(list.size()> 0){
//			gotoScene(list[0]);
//		}
//	}else if (_mOverLayWorld.getScene(key) != NULL){
//		_mOverLayWorld.removeScene(key);
//		_mOverLayWorld.getSceneList(list);
//		if(list.size()> 0){
//			setOverLayScene(list[0]);
//		}
//	}
//}
//
//void TDisplayEngine::addOverLayScene(std::string key,scene* s){
//	_mOverLayWorld.addScene(key, s);
//}
//
//void TDisplayEngine::setOverLayScene(std::string key) {
//	_mOverLayWorld.gotoScene(key);
//	_mOverLayWorld.resize(scr_size[0],scr_size[1]);
//}
//
//void TDisplayEngine::setBackGround(std::string image) {
//	_bg = image;
//	repaintScreen = true;
//}
//
//std::string TDisplayEngine::getBackGround() {
//	return _bg;
//}
//
//bool TDisplayEngine::sceneExists(std::string key){
//	if(_world.getScene(key)==NULL){
//		return _mOverLayWorld.getScene(key) != NULL;
//	}else{
//		return true;
//	}
//	
//	return false;
//}
//
//int TDisplayEngine::pick(world* w, select_buffer *_select_buffer) {
//	_select_buffer->_size = 0;
//	camera* cam = w->getCamera();
//	if (cam == NULL){
//		return 0;
//	}
//	_picking = true;
//	int hits = 0; // The Number Of Entitys That We Selected
//
//	// int[] buffer = new int[BUFSIZE]; // Set Up A Selection Buffer
//	GLuint buf[512];
//
//	// The Size Of The Viewport. [0] Is <x>, [1] Is <y>, [2] Is <length>,
//	// [3] Is <width>
//	int viewport[4];
//	glGetIntegerv(GL_VIEWPORT, viewport);
//
//	glSelectBuffer(512, buf); // Tell OpenGL To Use Our
//
//	// Array For Selection
//	// Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Entity ID's and
//	// Extents Are Stored In The Buffer.
//	glRenderMode(GL_SELECT);
//
//	glInitNames(); // Initializes The Name Stack
//	glPushName(0); // Push 0 (At Least One Entry) Onto The Stack
//	glMatrixMode(GL_PROJECTION); // Selects The Projection Matrix
//	glPushMatrix(); // Push The Projection Matrix
//	glLoadIdentity(); // Resets The Matrix
//	// This Creates A Matrix That Will Zoom Up To A Small Portion Of The
//	// Screen, Where The Mouse Is.
//
//	gluPickMatrix(_mouse_event.x, (viewport[3] - _mouse_event.y), 1.0, 1.0,
//			viewport);
//
//	// Apply The Perspective Matrix
//	//__setProjection_(viewport[2],viewport[3])
//	cam->apply_perspective();
//
//	glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
//	glLoadIdentity();
//	// Render The Targets To The Selection Buffer
//	cam->apply_view();
//	w->render();
//
//	glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
//	glPopMatrix(); // Pop The Projection Matrix
//	glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
//	hits = glRenderMode(GL_RENDER); // Switch To Render Mode, Find Out
//
//	_picking = false;
//
//	_select_buffer->_size = 0;
//	if (hits>0) {
//
//		tri::utils::bubble_sort_list_4_uint32_decend(buf, hits*4, 2);
//
//		int count = hits*4-1;
//		_select_buffer->_size = hits;
//		if (hits>MAX_SELECT_BUFFER_SIZE) {
//			count = MAX_SELECT_BUFFER_SIZE*4-1;
//			_select_buffer->_size = MAX_SELECT_BUFFER_SIZE;
//		}
//
//		int j=0;
//		for (int i=count; i>0; i-=4) {
//			_select_buffer->buf[j] = buf[i];
//			j++;
//		}
//
//	}
//	return hits;
//}
//
//int world::world_uid_count = 0;
//world::world(TDisplayEngine* de) :
//	scene_cache(), last_scene(""), _de(de) {
//	world_uid_count++;
//	world_uid = world_uid_count;
//}
//world::~world() {
//}
//
//int world::get_world_id() {
//	return this->world_uid;
//}
//
//void world::update() {
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		if (s->getName().compare(last_scene.c_str())!=0) {
//			scene* lc = this->getScene(last_scene);
//			if (lc!=NULL) {
//				lc->on_scene_close();
//			}
//			assert(s->getRootNode()==this);
//			int w, h;
//			getScreenSize(w,h);
//			s->on_screen_resize(w,h);
//			s->on_scene_open();
//		}
//		last_scene = s->getName();
//		s->update();
//	} else {
//		last_scene = "";
//	}
//}
//
//void world::postDisplayUpdate(){
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		s->postDisplayUpdate();
//	}
//}
//
//
//bool world::sceneChanged(){
//	bool ret = false;
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		ret = s->getName().compare(last_scene.c_str())!=0;
//	}
//	return ret;
//}
//
//bool world::repaintRequested(){
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		return s->repaintRequested();
//	}
//	return false;
//}
//
//camera* world::getCamera(){
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		return s->getCamera();
//	}
//	
//	return NULL;
//}
//
//void world::render() {
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		s->updateModelViewMatrix();
//		s->preDisplayUpdate();
//		s->render();
//	}
//}
//
//void world::getScreenSize(int&w, int&h) {
//	w = scr_size[0];
//	h = scr_size[1];
//}
//
//void world::resize(int width, int height) {
//	scr_size[0] = width;
//	scr_size[1] = height;
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		s->on_screen_resize(width,height);
//	}
//}
//
//void world::setBackGround(std::string image) {
//	if (_de !=NULL) {
//		_de->setBackGround(image);
//	}
//}
//
//std::string world::getBackGround() {
//	if (_de !=NULL) {
//		return _de->getBackGround();
//	}
//	return "";
//}
//
//void world::keyDown(int key){
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		s->KEY_PRESSED.emit(key);
//	}
//}
//
//void world::keyUp(int key){
//	scene* s = this->get_cur_scene();
//	if (s!=NULL) {
//		s->KEY_RELEASED.emit(key);
//	}
//}

}
